使用繼承雖然可以重複使用程式碼,但是繼承會有一些缺點,像是:
上面的兩個缺點,都顯示了一個問題,繼承容易有不預期的結果。以下面的範例來說,InstrumentedHashSet繼承了HashSet,並用addCount紀錄有多少item被加進去set,但因為HashSet在執行addAll的時候會呼叫add,所以addCount被重複計算,也許可以重寫addAll或add邏輯,甚至在加一個新的方法改善這個問題,但卻都不是好的解決方式。
import java.util.Arrays;
import java.util.Collection; 
import java.util.HashSet;
// Broken - Inappropriate use of inheritance!
public class InstrumentedHashSet<E> extends HashSet<E> {
    // The number of attempted element insertions
    private int addCount = 0;
    public InstrumentedHashSet() {
    }
    public InstrumentedHashSet(int initCap, float loadFactor) {
      super(initCap, loadFactor);
    }
    @Override public boolean add(E e) {
      addCount++;
      return super.add(e);
    }
    @Override public boolean addAll(Collection<? extends E> c) {
      addCount += c.size();
      return super.addAll(c);
    }
    public int getAddCount() {
      return addCount;
    }
    public static void main(String[] args) {
        InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
        s.add("a");
        s.add("b");
        s.addAll(Arrays.asList("c", "d", "e"));
        System.out.println(s.getAddCount()); // 應該輸出 5, 但是結果是 8
        System.out.println(s); // 應該輸出 [a, b, c, d, e]
    }
}
因為改寫和加方法也有可能遇到一些問題:
由於上述的缺點,繼承比較適合在package裡面小規模使用,如果類別的使用範圍很廣,也許使用composition去實作需要擴充的功能,建立一個新的類別,並把原有類別的一個欄位指到新的類別,新類別就可以作為該模組的一個新功能,是一個比較好的選擇。
上面的範例原本是繼承HashSet來擴充功能,但可以改成實作Set這個interface,並宣告set這個欄位指向HashSet的物件,接著實作Set的方法時,如果需要改變方法的行為,完全可以依照自己的邏輯實作,但如果不需要改變方法的行為,也可以直接回傳set的執行結果,重複使用HashSet的程式碼。
import java.util.*;
public class InstrumentedHashSet<E> implements Set<E> {
    // The number of attempted element insertions
    private int addCount = 0;
    private final Set<E> set;
    public InstrumentedHashSet() {
        set = new HashSet<>();
    }
    public boolean add(E e) {
        addCount++;
        return set.add(e);
    }
    public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return set.addAll(c);
    }
    public int getAddCount() {
        return addCount;
    }
    @Override
    public String toString() {
        return set.toString();
    }
    public int size() {
        return set.size();
    }
    public boolean isEmpty() {
        return set.isEmpty();
    }
    public boolean contains(Object o) {
        return set.contains(o);
    }
    public Iterator<E> iterator() {
        return set.iterator();
    }
    public Object[] toArray() {
        return set.toArray();
    }
    public <T> T[] toArray(T[] a) {
        return set.toArray(a);
    }
    public boolean remove(Object o) {
        return set.remove(o);
    }
    public boolean containsAll(Collection<?> c) {
        return set.containsAll(c);
    }
    public boolean retainAll(Collection<?> c) {
        return set.retainAll(c);
    }
    public boolean removeAll(Collection<?> c) {
        return set.removeAll(c);
    }
    public void clear() {
        set.clear();
    }
    
    public static void main(String[] args) {
        InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
        s.add("a");
        s.add("b");
        s.addAll(Arrays.asList("c", "d", "e"));
        System.out.println(s.getAddCount()); // 應該輸出 5
        System.out.println(s); // 應該輸出 [a, b, c, d, e]
    }
}
今天就介紹到這裡,明天會介紹使用composition改寫繼承更彈性的作法~